#include "log.h"

#include <chrono>

// 文件名中日期可用最大长度 
const int DATE_TAIL_MAX_LEN = 36;
// 日志文件名可用缓冲区长度
const int LOG_FILE_NAME_AVAILABLE_LEN = 72;
// 缓冲区中日期可占的最大字节数
const int MAX_TIME_FORMAT_LEN = 128;
Log::Log()
{
    curlines_ = 0;
    isAsync_ = false;
    writeThread_ = nullptr;
    deque_ = nullptr;
    curDays_ = 0;
    fp_ = nullptr;
}

Log::~Log() 
{

}

int Log::getLevel()
{
    std::lock_guard<std::mutex> lock(mtx_);
    return level_;
}

void Log::setLevel(int level)
{
    std::lock_guard<std::mutex> lock(mtx_);
    level_ = level;
}

bool Log::isOpen()
{
    return isOpen_;
}

// 初始化
void Log::init(int level, const char* path, 
            const char* suffix, int maxQueueSize)
{
    isOpen_ = true;
    level_  = level;

    // 开启异步日志
    if(maxQueueSize > 0) {
        isAsync_ = true;
        // 阻塞队列为空则创建阻塞队列及异步线程
        if(!deque_) {
            std::unique_ptr<BlockQueue<std::string>> newDeque(new BlockQueue<std::string>);
            deque_ = std::move(newDeque);

            std::unique_ptr<std::thread> newThread(new std::thread(flushLogThread));
            writeThread_ = std::move(newThread);
        } else {
            isAsync_ = false;
        }
    }

    curlines_ = 0;

    // 获取当前时间戳
    time_t timer = time(nullptr);
    // 将时间戳转换为本地时间
    struct tm* sysTime = localtime(&timer);
    struct tm t = *sysTime;

    // 拼接文件名 : 路径 + 年月日 + 后缀
    path_ = path;
    suffix_ = suffix;
    char fileName[MAX_LOG_NAME_LEN] = {0};
    snprintf(fileName, MAX_LOG_NAME_LEN - 1, "%s/%04d_%02d_%02d%s", 
        path_, t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, suffix_);
    curDays_ = t.tm_mday;

    {
        std::lock_guard<std::mutex> lcok(mtx_);
        // 清空缓冲区
        buff_.retrieveAll();
        if(fp_) {
            flush();
            fclose(fp_);
        }

        // 重新打开文件
        fp_ = fopen(fileName, "a");
        if(fp_ == nullptr) {
            mkdir(path_, 0777);
            fp_ = fopen(fileName, "a");
        }
        assert(fp_ != nullptr);
    }
}

// 写日志
void Log::write(int level, const char* format, ...)
{
    // 获取当前精确时间，精确到微秒
    struct timeval now = {0, 0};
    gettimeofday(&now, nullptr);
    // 分离秒数和微秒
    time_t tSec = now.tv_sec;
    struct tm* sysTime = localtime(&tSec);
    struct tm t = *sysTime;

    // 存储可变参数的信息
    va_list vaList;

    // 检查是否需要重新创建日志文件(日期更新或者行数超过限制)
    if(curDays_ != t.tm_mday || (curlines_ && (curlines_ % MAX_LINES == 0)))
    {
        // 避免生产新文件名时占用锁
        std::unique_lock<std::mutex> lock(mtx_);
        lock.unlock();

        char newFile[MAX_LOG_NAME_LEN];
        // 生成日志文件名的日期部分 2003_02_13
        char tail[DATE_TAIL_MAX_LEN];
        snprintf(tail, DATE_TAIL_MAX_LEN, "%04d_%02d_%02d", 
                    t.tm_year + 1900, t.tm_mon + 1, t.tm_mday);
        
        // 拼接新的文件名
        if(curDays_ != t.tm_mday) 
        {
            snprintf(newFile, MAX_LOG_NAME_LEN - LOG_FILE_NAME_AVAILABLE_LEN, 
                    "%s/%s%s", path_, tail, suffix_);
            curDays_ = t.tm_mday;
            curlines_ = 0;
        } else {
            // 添加标志位显示为第几个溢出文件
            snprintf(newFile, MAX_LOG_NAME_LEN - LOG_FILE_NAME_AVAILABLE_LEN,
                    "%s/%s-%d%s", path_, tail, (curlines_ / MAX_LINES), suffix_);
        }

        lock.lock();
        flush();
        fclose(fp_);
        fp_ = fopen(newFile, "a");
        assert(fp_ != nullptr);
    }

    // 不需要创建新日志文件
    {
        // 写入准确时间 
        std::unique_lock<std::mutex> lock(mtx_);
        curlines_++;
        int n = snprintf(buff_.beginWrite(), MAX_TIME_FORMAT_LEN,
            "%d-%02d-%02d %02d:%02d:%02d.%06ld", t.tm_year + 1900, 
            t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, now.tv_usec);
        buff_.hasWritten(n);
        // 添加日志等级标题
        appendLogLevelTitle(level);
    
        // 初始化可变参列表，使其指向formt之后的那个参数
        va_start(vaList, format);
        // 根据格式化字符串 format 和可变参数列表 vaList，将格式化后的内容写入到指定的缓冲区
        int m = vsnprintf(buff_.beginWrite(), buff_.writableBytes(), format, vaList);
        // 清空可变参列表
        va_end(vaList);

        buff_.hasWritten(m);
        buff_.append("\n\0", 2);

        // 使用阻塞队列异步写入日志
        if(isAsync_ && deque_ && !deque_->full()) {
            deque_->push_back(buff_.retrieveAllToStr());
        } else {
            // 直接写入日志文件
            fputs(buff_.peek(), fp_);
        } 
        buff_.retrieveAll();
    }
}

// 添加日志级别标题
void Log::appendLogLevelTitle(int level)
{
    switch (level)
    {
    case 0:
        buff_.append("[DEBUG]: ", 9);
        break;
    case 1:
        buff_.append("[INFO] : ", 9);
        break;
    case 2:
        buff_.append("[WARN] : ", 9);
        break;
    case 3:
        buff_.append("[ERROR]: ", 9);
        break;
    default:
        buff_.append("[DEBUG]: ", 9);
        break;
    }
}

// 将日志数据从缓冲区保存到文件
void Log::flush()
{
    // 异步模式唤醒线程写日志
    if(isAsync_) {
        deque_->flush();
    }
    fflush(fp_);
}

// 异步写入日志信息
void Log::asyncWrite()
{
    std::string str = "";
    // 只要阻塞队列有数据
    while(deque_->pop(str)) {
        std::lock_guard<std::mutex> lock(mtx_);
        fputs(str.c_str(), fp_);
    }
}


// 实现了单例模式
Log* Log::instance() 
{
    static Log inst;
    return &inst;
}

// 启动日志刷新线程
void Log::flushLogThread()
{
    Log::instance()->asyncWrite();
}

#if 0
int main()
{
    Log::instance()->init(1, "./log", ".log", 1000);
    while(true) {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        LOG_INFO("async test");
    }
    return 0;
}


int main()
{
    Log::instance()->init(1, "./log", ".log", 1000);
    while(true) {
        LOG_INFO("async test");
    }
    return 0;
}
#endif